package widget.badge;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.BounceInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import library.image.ImageUtils;
import library.logger.Logger;


public class BadgeBezier extends FrameLayout {
    private static final ConcurrentHashMap<String, WeakReference<BadgeBezier>> map = new ConcurrentHashMap<>();
    private static final float MAX_SCALE = 1.05f;

    private static final String DEFAULT_ANIM = "widget/images/badge/badge_bubble.zip";
    private ImageView dragView;
    private boolean isMove, isTouch, isTrigger;
    private Object anim = DEFAULT_ANIM;
    private Paint paint;
    private Path path;
    private float corner = 0;
    private float radius = corner;

    private float dragX = 0;
    private float dragY = 0;

    private float anchorX = 0;
    private float anchorY = 0;

    private float startX = 0;
    private float startY = 0;

    private ValueAnimator mBounceAnimator = null;

    public BadgeBezier(Context context) {
        super(context);
        init();
    }

    public BadgeBezier(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BadgeBezier(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public BadgeBezier(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        setBackgroundColor(Color.TRANSPARENT);
        path = new Path();

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.RED);

        LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        dragView = new ImageView(getContext());
        dragView.setLayoutParams(lp);
        dragView.setVisibility(View.INVISIBLE);
        dragView.setScaleType(ImageView.ScaleType.FIT_CENTER);
        dragView.setFocusable(false);
        dragView.setFocusableInTouchMode(false);
        addView(dragView);
    }

    public int dp2px(float dipValue) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    private void calculate() {
        int width = getWidth();
        int height = getHeight();
        int dMax = Math.min(width, height) / 3;
        int rMin = dp2px(3);
        float distance = (float) Math.sqrt(Math.pow(dragY - startY, 2) + Math.pow(dragX - startX, 2));
        radius = corner - corner * distance / dMax;
        radius = Math.max(rMin, radius);
        isTrigger = distance > dMax;

        float offsetX = (float) (radius * Math.sin(Math.atan((dragY - startY) / (dragX - startX))));
        float offsetY = (float) (radius * Math.cos(Math.atan((dragY - startY) / (dragX - startX))));

        float x1 = startX - offsetX;
        float y1 = startY + offsetY;

        float x2 = dragX - offsetX;
        float y2 = dragY + offsetY;

        float x3 = dragX + offsetX;
        float y3 = dragY - offsetY;

        float x4 = startX + offsetX;
        float y4 = startY - offsetY;

        path.reset();
        path.moveTo(x1, y1);
        path.quadTo(anchorX, anchorY, x2, y2);
        path.lineTo(x3, y3);
        path.quadTo(anchorX, anchorY, x4, y4);
        path.lineTo(x1, y1);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (isMove && dragView != null && dragView.getVisibility() == VISIBLE) {
            calculate();
            if (isTrigger || !isTouch || dragView == null) {
                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC_OVER);
            } else {
                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC_OVER);
                canvas.drawPath(path, paint);
                canvas.drawCircle(startX, startY, radius, paint);
                canvas.drawCircle(dragX, dragY, radius, paint);
            }
        }
        super.onDraw(canvas);
    }

    private void _attach(View target, final OnDismissListener onDismissListener) {
        target.setOnTouchListener(new OnTouchListener() {
            private int thisX;
            private int thisY;

            private float downX;
            private float downY;

            @Override
            public boolean onTouch(View target, MotionEvent event) {
                int action = event.getAction();
                if (action == MotionEvent.ACTION_DOWN) {
                    isTouch = true;
                    isMove = false;
                    downX = event.getRawX();
                    downY = event.getRawY();
                    if (mBounceAnimator != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                        mBounceAnimator.cancel();
                    }
                    target.getParent().requestDisallowInterceptTouchEvent(true);
                    corner = Math.min(target.getWidth(), target.getHeight()) / 2;
                    if (target instanceof BadgeView) {
                        paint.setColor(((BadgeView) target).getBadgeColor());
                    } else if (target instanceof TextView) {
                        paint.setColor(target.getDrawingCacheBackgroundColor());
                    }
                    int[] targetLocation = new int[2];
                    target.getLocationOnScreen(targetLocation);
                    int[] frameLocation = new int[2];
                    getLocationOnScreen(frameLocation);
                    thisX = frameLocation[0];
                    thisY = frameLocation[1];

                    startX = targetLocation[0] - frameLocation[0] + target.getWidth() / 2;
                    startY = targetLocation[1] - frameLocation[1] + target.getHeight() / 2;

                    Bitmap bitmap = view2Bitmap(target);
                    dragView.setImageBitmap(bitmap);
                    dragView.setVisibility(VISIBLE);
                    target.setVisibility(INVISIBLE);
                    ViewCompat.setX(dragView, startX - target.getWidth() / 2);
                    ViewCompat.setY(dragView, startY - target.getHeight() / 2);
                    ViewCompat.setScaleX(dragView, MAX_SCALE);
                    ViewCompat.setScaleY(dragView, MAX_SCALE);
                } else if (action == MotionEvent.ACTION_MOVE) {
                    isMove = true;
                    final float offsetX = event.getRawX() - downX;
                    final float offsetY = event.getRawY() - downY;
                    final int rx = dragView.getWidth() / 2;
                    final int ry = dragView.getHeight() / 2;
                    int pad = dp2px(8);

                    dragX = startX + offsetX - thisX;
                    dragX = Math.max(dragX, thisX + pad + rx);
                    dragX = Math.min(dragX, thisX + getWidth() - pad - rx);

                    dragY = startY + offsetY - thisY;
                    dragY = Math.max(dragY, thisY + pad + ry);
                    dragY = Math.min(dragY, thisY + getHeight() - pad - ry);

                    anchorX = (dragX + startX) / 2;
                    anchorY = (dragY + startY) / 2;

                    float setX = dragX - dragView.getWidth() / 2;
                    float setY = dragY - dragView.getHeight() / 2;
                    ViewCompat.setX(dragView, setX);
                    ViewCompat.setY(dragView, setY);
                    invalidate();
                } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                    isTouch = false;
                    radius = 0;
                    if (isTrigger) {
                        AnimationDrawable anim = getBubbleAnimation();
                        dragView.setImageDrawable(anim);
                        anim.start();
                        if (onDismissListener != null)
                            onDismissListener.onDismiss(target);
                    } else {
                        dragView.setImageBitmap(null);
                        dragView.setVisibility(INVISIBLE);
                        target.setVisibility(VISIBLE);
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                            mBounceAnimator = ObjectAnimator.ofFloat(1f - 0.2f, 1f);
                            mBounceAnimator.setDuration(200);
                            mBounceAnimator.setRepeatCount(0);
                            mBounceAnimator.setInterpolator(new BounceInterpolator());
                            mBounceAnimator.addUpdateListener(animation -> {
                                float value = (float) animation.getAnimatedValue();
                                ViewCompat.setScaleX(target, value);
                                ViewCompat.setScaleY(target, value);
                            });
                            mBounceAnimator.start();
                        } else {
                            ViewCompat.setScaleX(target, 1f);
                            ViewCompat.setScaleY(target, 1f);
                        }
                    }
                }
                return true;
            }
        });
    }

    public static Bitmap view2Bitmap(View v) {
        Bitmap bm = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bm);
        v.draw(canvas);
        return bm;
    }

    private AnimationDrawable getBubbleAnimation() {
        Map<String, Drawable> map = new TreeMap<>((String key1, String key2) -> {
            return key1.compareToIgnoreCase(key2);
        });
        InputStream is = getInputStream(getContext(), anim);
        ZipInputStream zin = null;
        try {
            zin = new ZipInputStream(is);
            ZipEntry ze = null;

            while ((ze = zin.getNextEntry()) != null) {
                Drawable d = getAssetsDrawable(ze.getName(), zin);
                if (d != null)
                    map.put(ze.getName(), d);
                zin.closeEntry();
            }
            zin.close();
        } catch (Exception e) {
            Logger.debug(e);
        } finally {
            if (zin != null)
                try {
                    zin.close();
                } catch (IOException e) {
                }
        }
        final int duration = 200;
        AnimationDrawable ani = new AnimationDrawable();
        for (Map.Entry<String, Drawable> entry : map.entrySet()) {
            ani.addFrame(entry.getValue(), duration);
        }
        ani.addFrame(new ColorDrawable(Color.TRANSPARENT), duration);
        ani.setOneShot(true); //设置为loop
        return ani;
    }

    private Drawable getAssetsDrawable(String flag, InputStream is) {
        Bitmap bitmap = ImageUtils.obtain(getContext())
                .tag(this)
                .flag(flag)
                .inputStream(is, false)
                .load();
        return new BitmapDrawable(bitmap);
    }


    @Override
    protected void onDetachedFromWindow() {
        ImageUtils.cancel(this);
        if (mBounceAnimator != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            mBounceAnimator.cancel();
        }
        synchronized (map) {
            map.remove(key(getContext()));
        }
        super.onDetachedFromWindow();
    }

    public static boolean attach(final View target) {
        return attach(target, null);
    }

    public static boolean attach(final View target, OnDismissListener onDismissListener) {
        Context context = target.getContext();
        BadgeBezier frame;
        synchronized (map) {
            WeakReference<BadgeBezier> reference = map.get(key(context));
            frame = reference != null ? reference.get() : null;
            if (frame == null) {
                frame = addFrame(target);
            }
            if (frame != null) {
                map.put(key(context), new WeakReference<>(frame));
                frame._attach(target, onDismissListener);
                return true;
            }
        }
        return false;
    }

    private static BadgeBezier addFrame(View target) {
        Context context = target.getContext();
        View root = target.getRootView();
        ViewGroup content;
        content = (ViewGroup) root.findViewById(Window.ID_ANDROID_CONTENT);
        if (content == null && context instanceof Activity) {
            Activity activity = (Activity) context;
            ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
            content = (ViewGroup) decor.findViewById(Window.ID_ANDROID_CONTENT);
        }
        if (content != null) {
            LayoutParams lp = new LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT);
            BadgeBezier frame = new BadgeBezier(context);
            content.addView(frame, lp);
            return frame;
        }
        return null;
    }

    private static String key(Context context) {
        return String.valueOf(context);
    }

    public interface OnDismissListener {
        void onDismiss(View view);
    }


    public static InputStream getInputStream(Context context, Object obj) {
        InputStream is = null;
        try {
            if (obj instanceof InputStream)
                is = (InputStream) obj;
            else if (obj instanceof String) {
                String path = (String) obj;
                if (!path.startsWith("/")) {
                    try {
                        is = context.getAssets().open(path);
                    } catch (Exception e) {

                    }
                }
                if (is == null) {
                    try {
                        is = new FileInputStream(path);
                    } catch (Exception e) {

                    }
                }
                if (is == null) {
                    is = new ByteArrayInputStream(path.getBytes());
                }
            } else if (obj instanceof Integer) {
                int id = (Integer) obj;
                is = context.getResources().openRawResource(id);
            }
        } catch (Exception e) {
            Logger.debug(e);
        }
        return is;
    }

}
